/*
 * Copyright 2007 National Bioinformatics Network
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dgbgui.gui.widgets.visual;

import java.awt.BasicStroke;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import dgbgui.gui.shapes.RoundedRectangleShape;
import dgbgui.gui.widgets.HasEdgePort;
import dgbgui.gui.widgets.Widget;
import dgbgui.gui.widgets.WidthAdjustable;
import dgbgui.gui.widgets.layouts.HorizontalLayout;
import dgbgui.gui.widgets.layouts.Layout;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.util.PBounds;
import edu.umd.cs.piccolo.util.PPaintContext;

@SuppressWarnings("serial")
public class RectangleBorder extends Widget implements WidthAdjustable, HasEdgePort {

    public RectangleBorder(PNode parent) {
        this(parent,5.0,5.0);
    }
    
    public RectangleBorder(PNode parent, double topRadius, double bottomRadius) {
        super(parent);
        m_topRadius = topRadius;
        m_bottomRadius = bottomRadius;
        m_layout = new HorizontalLayout(this,5.0,5.0);
    }
    
    /**
     * Change the default paint to fill an expanded bounding box based on its children's bounds
     */
    public void paint(PPaintContext ppc) {
        
        Graphics2D g2 = ppc.getGraphics();
        g2.setStroke(new BasicStroke(0));


        PBounds bounds = computeFullBounds(null);
        parentToLocal(bounds);
        
        Shape roundedShape = new RoundedRectangleShape(
                bounds.getX(),bounds.getY(),
                bounds.getWidth(),bounds.getHeight(),
                m_topRadius,m_bottomRadius);
        
        //  Fill the shape
        g2.setPaint(getFillColour());
        g2.fill(roundedShape);
        
        // Draw the borders
        g2.setPaint(getStrokeColour());
        g2.setStroke(getStrokeStyle());
        g2.draw(roundedShape);

        
    }
                
    /**
     * Change the full bounds computation to take into account that we are expanding the children's bounds
     * Do this instead of overriding getBoundsReference() since the node is not volatile
     */
    public PBounds computeFullBounds(PBounds dstBounds) {
        PBounds result = getUnionOfChildrenBounds(dstBounds);
        m_cachedChildBounds.setRect(result);  
        
        if ( result.getWidth() < m_absoluteWidth ) {
            result.setRect(result.getX(),result.getY(),m_absoluteWidth,result.getHeight());
        } else {
            result.setRect(result.getX(),result.getY(),result.getWidth(),result.getHeight());
        }
        
        localToParent(result);
        return result;      
    }
                        
    /**
     * This is a crucial step.  We have to override this method to invalidate the paint each time the bounds are changed so
     * we repaint the correct region
     */
    public boolean validateFullBounds() {
        m_comparisonBounds = getUnionOfChildrenBounds(m_comparisonBounds);
    
        if (!m_cachedChildBounds.equals(m_comparisonBounds)) {
            setPaintInvalid(true);
        }
        return super.validateFullBounds();  
    }
    
    /**  
     * Non rectangular subclasses need to override this method so
     * that they will be picked correctly and will receive the
     * correct mouse events.
     */
    public boolean intersects(Rectangle2D aBounds) {
        PBounds bounds = computeFullBounds(null);
        parentToLocal(bounds);
        return bounds.intersects(aBounds);
    }
    
    public void setAbsoluteWidth(double width) {
        m_absoluteWidth = width;
        m_layout.layoutChildren();
    }

    
    public double getAbsoluteWidth() {
        return m_absoluteWidth;
    }
    

    private Point2D getIntersectPoint(Line2D a, Line2D b) {
        
        boolean aVertical = ( a.getX1() == a.getX2() );
        double m1 = (a.getY2() - a.getY1())  / (a.getX2() - a.getX1());
        double c1 = (a.getY1() - m1 * a.getX1());


        boolean bVertical = ( b.getX1() == b.getX2() );
        double m2 = (b.getY2() - b.getY1())  / (b.getX2() - b.getX1());
        double c2 = (b.getY1() - m2 * b.getX1()); 

        assert(!(aVertical && bVertical));

        if ( aVertical ) {
            return new Point2D.Double(
                    a.getX1(),
                    m2*a.getX1()+ c2); 
        }
        else if ( bVertical ) {
            return new Point2D.Double(
                    b.getX1(),
                    m1*b.getX1()+ c1); 
        }

        // Solve the following equations:
        // y = m1*x + c1 
        // y = m2*x + c2
        // => 0 = m1*x - m2*x + c1 - c2
        // => c2 - c1 = x*(m1 - m2)
        // => x = (c2 - c1)/(m1 - m2)
        // => y = 
        
        double x = (c2 - c1)/(m1 - m2); 
        return new Point2D.Double(
                x,
                m1*x + c1);
        
    }
    
    /**
     * @param line
     * @return The edge attachment port for given edge
     */
    public Point2D getPort(Line2D line) { 
        // If the edge intersect the rectangle border, the
        // port is on the intersection point, except on the corners
        // where the port is on the center of the curve
        Rectangle2D bounds = getFullBounds();
        if ( line.intersects(getFullBounds())) {
            if ( m_topRadius > 0 ) {

                double temp = m_topRadius*(1.0 - 1/Math.sqrt(2));
                
                // ----- Top left ----- 
                Rectangle2D topLeft = new Rectangle2D.Double(
                        bounds.getX(),
                        bounds.getY(),
                        m_topRadius,
                        m_topRadius);

                if ( topLeft.intersectsLine(line)) {
                    return new Point2D.Double(
                            bounds.getX() + temp,
                            bounds.getY() + temp);
                }
                
                // ----- Top right ----- 
                Rectangle2D topRight = new Rectangle2D.Double(
                        bounds.getX() + bounds.getWidth() - m_topRadius,
                        bounds.getY(),
                        m_topRadius,
                        m_topRadius);

                if ( topRight.intersectsLine(line)) {
                    return new Point2D.Double(
                            bounds.getX() + bounds.getWidth() - temp,
                            bounds.getY()  + temp);
                }
            }
            
            if ( m_bottomRadius > 0 ) {
                
                double temp = m_bottomRadius*(1.0 - 1/Math.sqrt(2));
                
                // ----- Bottom left ----- 
                Rectangle2D bottomLeft = new Rectangle2D.Double(
                        bounds.getX(),
                        bounds.getY() + bounds.getHeight() - m_bottomRadius,
                        m_bottomRadius,
                        m_bottomRadius);

                if ( bottomLeft.intersectsLine(line)) {
                    return new Point2D.Double(
                            bounds.getX() + temp,
                            bounds.getY() + bounds.getHeight() - temp);
                }
                
                // ----- Bottom right ----- 
                Rectangle2D bottomRight = new Rectangle2D.Double(
                        bounds.getX() + bounds.getWidth() - m_bottomRadius,
                        bounds.getY() + bounds.getHeight() - m_bottomRadius,
                        m_bottomRadius,
                        m_bottomRadius);

                if ( bottomRight.intersectsLine(line)) {
                    return new Point2D.Double(
                            bounds.getX() + bounds.getWidth() - temp,
                            bounds.getY() + bounds.getHeight() - temp);
                }
            }
            
            // ----- Top, bottom, left and right -----
            Line2D top = new Line2D.Double(
                    bounds.getX(),bounds.getY(),
                    bounds.getX() + bounds.getWidth(),bounds.getY());
            if ( line.intersectsLine(top) ) {
                return getIntersectPoint(line,top);
            }

            Line2D bottom = new Line2D.Double(
                    bounds.getX(),bounds.getY() + bounds.getHeight(),
                    bounds.getX() + bounds.getWidth(),bounds.getY() + bounds.getHeight());
            if ( line.intersectsLine(bottom) ) {
                Point2D temp = getIntersectPoint(line,bottom); 
                return temp;
            }

            Line2D left = new Line2D.Double(
                    bounds.getX(),bounds.getY(),
                    bounds.getX(),bounds.getY() + bounds.getHeight());
            if ( line.intersectsLine(left) ) {
                return getIntersectPoint(line,left);
            }

            Line2D right = new Line2D.Double(
                    bounds.getX() + bounds.getWidth(),bounds.getY(),
                    bounds.getX() + bounds.getWidth(),bounds.getY() + bounds.getHeight());
            if ( line.intersectsLine(right) ) {
                return getIntersectPoint(line,right);
            }
                    
        }
        return getFullBounds().getCenter2D();
    }
    
    
    PBounds m_cachedChildBounds = new PBounds();
    PBounds m_comparisonBounds = new PBounds();
    
    // The rounding radius
    double m_topRadius,m_bottomRadius = 0;
    
    // The minimum width
    double m_absoluteWidth = 0;
    

    protected Layout m_layout;
}
